(in-package #:coalton-native-tests)

(coalton-toplevel
  (declare list->ordmap (Ord :k => List (Tuple :k :v) -> ordmap:OrdMap :k :v))
  (define (list->ordmap xs)
    (iter:collect!
     (iter:into-iter
      (the (List (Tuple :a :b)) xs))))

  (declare ordmap->list (ordmap:OrdMap :k :v -> List (Tuple :k :v)))
  (define (ordmap->list m) (iter:collect! (iter:into-iter m)))
  )

(define-test ordmap-basic-test ()
  (is (== (ordmap:lookup (the (ordmap:OrdMap Integer String) ordmap:empty) 0)
          None))

  (is (== (let ((t (ordmap:insert ordmap:empty 0 "Zero")))
            (ordmap:lookup t 0))
          (Some "Zero")))

  (is (== (let ((t (ordmap:insert ordmap:empty 0 "Zero")))
            (ordmap:lookup t 1))
          None))

  (is (== (let ((t (ordmap:insert ordmap:empty 0 "Zero"))
                (t2 (ordmap:insert t 0 "Null")))
            (ordmap:lookup t2 0))
          (Some "Null")))

  (is (== (let ((t (ordmap:insert ordmap:empty 0 "Zero"))
                (t2 (ordmap:adjoin t 0 "Null")))
            (ordmap:lookup t2 0))
          (Some "Zero")))

  (is (== (let ((t (ordmap:insert ordmap:empty 0 "Zero"))
                (t2 (ordmap:replace t 0 "Null")))
            (ordmap:lookup t2 0))
          (Some "Null")))

  (is (== (let ((t (ordmap:insert ordmap:empty 0 "Zero"))
                (t2 (ordmap:replace t 1 "One")))
            (ordmap:lookup t2 1))
          None))

  (let ((src (the (ordmap:OrdMap Integer String)
                  (iter:collect!
                   (iter:into-iter
                    (make-list (Tuple 0 "Zero")
                               (Tuple 1 "One")
                               (Tuple 2 "Two"))))))
        (a (ordmap:remove src 1))
        (b (ordmap:remove src 3)))
    (is (== (make-list (ordmap:lookup a 0)
                       (ordmap:lookup a 1)
                       (ordmap:lookup a 2))
            (make-list (Some "Zero") None (Some "Two"))))
    (is (== (make-list (ordmap:lookup b 0)
                       (ordmap:lookup b 1)
                       (ordmap:lookup b 2))
            (make-list (Some "Zero") (Some "One") (Some "Two"))))

    (is (== (let ((t (ordmap:remove src 1))
                  (t2 (ordmap:remove t 2))
                  (t3 (ordmap:insert t2 1 "Eins")))
              (make-list (ordmap:lookup t3 0)
                         (ordmap:lookup t3 1)
                         (ordmap:lookup t3 2)))
            (make-list (Some "Zero") (Some "Eins") None))))

  (let ((src (list->ordmap (make-list (Tuple 1 "One")
                                      (Tuple 2 "Two")
                                      (Tuple 3 "Three")
                                      (Tuple 4 "Four")
                                      (Tuple 5 "Five")
                                      (Tuple 6 "Six")
                                      (Tuple 7 "Seven")))))
    (is (== (let ((a (ordmap:adjoin src 10 "Zehn"))
                  (b (ordmap:remove a 3))
                  (c (ordmap:remove b 7))
                  (d (ordmap:insert c 8 "Acht"))
                  (e (ordmap:remove d 1))
                  (f (ordmap:adjoin e 8 "Otto"))
                  (g (ordmap:remove f 10))
                  (h (ordmap:adjoin g 7 "Sieben"))
                  (i (ordmap:remove h 5)))
              (ordmap->list i))
            (make-list (Tuple 2 "Two")
                       (Tuple 4 "Four")
                       (Tuple 6 "Six")
                       (Tuple 7 "Sieben")
                       (Tuple 8 "Acht")))))

  ;; update
  (let ((src (list->ordmap (make-list (Tuple 1 "One")
                                      (Tuple 2 "Two")
                                      (Tuple 3 "Three")
                                      (Tuple 4 "Four")))))
    (is (== (match (ordmap:update src 4 (Tuple (Some "Quatre")))
              ((Tuple m v)
               (Tuple (ordmap->list m) v)))
            (Tuple (the (List (Tuple Integer String))
                        (make-list  (Tuple 1 "One")
                                    (Tuple 2 "Two")
                                    (Tuple 3 "Three")
                                    (Tuple 4 "Quatre")))
                   (Some "Four"))))
    (is (== (match (ordmap:update src 5 (Tuple (Some "Cinq")))
              ((Tuple m v)
               (Tuple (ordmap->list m) v)))
            (Tuple (the (List (Tuple Integer String))
                        (make-list  (Tuple 1 "One")
                                    (Tuple 2 "Two")
                                    (Tuple 3 "Three")
                                    (Tuple 4 "Four")
                                    (Tuple 5 "Cinq")))
                   None)))
    (is (== (match (ordmap:update src 3 (fn (v)
                                          (match v
                                            ((None) (Tuple (Some "Fill")
                                                           True))
                                            ((Some _) (Tuple v False)))))
              ((Tuple m v)
               (Tuple (ordmap->list m) v)))
            (Tuple (the (List (Tuple Integer String))
                        (make-list  (Tuple 1 "One")
                                    (Tuple 2 "Two")
                                    (Tuple 3 "Three")
                                    (Tuple 4 "Four")))
                   False)))
    (is (== (match (ordmap:update src 5 (fn (v)
                                          (match v
                                            ((None) (Tuple (Some "Fill")
                                                           True))
                                            ((Some _) (Tuple v False)))))
              ((Tuple m v)
               (Tuple (ordmap->list m) v)))
            (Tuple (the (List (Tuple Integer String))
                        (make-list  (Tuple 1 "One")
                                    (Tuple 2 "Two")
                                    (Tuple 3 "Three")
                                    (Tuple 4 "Four")
                                    (Tuple 5 "Fill")))
                   True))))
  )

(define-test ordmap-neighbors-test ()
  ;; min/max key
  (let ((src (list->ordmap (make-list (Tuple 1 "One")
                                   (Tuple 2 "Two")
                                   (Tuple 3 "Three")
                                   (Tuple 4 "Four")
                                   (Tuple 5 "Five")
                                   (Tuple 6 "Six")
                                   (Tuple 7 "Seven")))))
    (is (== (ordmap:max-key-entry src)
            (Some (Tuple 7 "Seven"))))
    (is (== (ordmap:min-key-entry src)
            (Some (Tuple 1 "One")))))
  (is (== (ordmap:max-key-entry (ordmap:insert ordmap:empty 1 "One"))
          (Some (Tuple 1 "One"))))
  (is (== (ordmap:min-key-entry (ordmap:insert ordmap:empty 1 "One"))
          (Some (Tuple 1 "One"))))
  (is (== (ordmap:max-key-entry (the (ordmap:OrdMap Integer String)
                                     ordmap:empty))
          None))
  (is (== (ordmap:min-key-entry (the (ordmap:OrdMap Integer String)
                                     ordmap:empty))
          None))

  ;; neighbors
  (let ((src (list->ordmap (make-list (Tuple 1 "One")
                                      (Tuple 3 "Three")
                                      (Tuple 5 "Five")
                                      (Tuple 7 "Seven")
                                      (Tuple 9 "Nine")))))
    (is (== (ordmap:lookup-neighbors src 0)
            (Tuple3 None None (Some (Tuple 1 "One")))))
    (is (== (ordmap:lookup-neighbors src 1)
            (Tuple3 None (Some (Tuple 1 "One")) (Some (Tuple 3 "Three")))))
    (is (== (ordmap:lookup-neighbors src 2)
            (Tuple3 (Some (Tuple 1 "One")) None (Some (Tuple 3 "Three")))))
    (is (== (ordmap:lookup-neighbors src 3)
            (Tuple3 (Some (Tuple 1 "One")) (Some (Tuple 3 "Three")) (Some (Tuple 5 "Five")))))
    (is (== (ordmap:lookup-neighbors src 4)
            (Tuple3 (Some (Tuple 3 "Three")) None (Some (Tuple 5 "Five")))))
    (is (== (ordmap:lookup-neighbors src 5)
            (Tuple3 (Some (Tuple 3 "Three")) (Some (Tuple 5 "Five")) (Some (Tuple 7 "Seven")))))
    (is (== (ordmap:lookup-neighbors src 6)
            (Tuple3 (Some (Tuple 5 "Five")) None (Some (Tuple 7 "Seven")))))
    (is (== (ordmap:lookup-neighbors src 7)
            (Tuple3 (Some (Tuple 5 "Five")) (Some (Tuple 7 "Seven")) (Some (Tuple 9 "Nine")))))
    (is (== (ordmap:lookup-neighbors src 8)
            (Tuple3 (Some (Tuple 7 "Seven")) None (Some (Tuple 9 "Nine")))))
    (is (== (ordmap:lookup-neighbors src 9)
            (Tuple3 (Some (Tuple 7 "Seven")) (Some (Tuple 9 "Nine")) None)))
    (is (== (ordmap:lookup-neighbors src 10)
            (Tuple3 (Some (Tuple 9 "Nine")) None None))))

  ;; edge cases
  (is (== (ordmap:lookup-neighbors (the (ordmap:OrdMap Integer String)
                                        ordmap:Empty)
                                   1)
          (Tuple3 None None None)))
  (is (== (let ((a (ordmap:insert ordmap:Empty 1 "One")))
            (ordmap:lookup-neighbors a 1))
          (Tuple3 None (Some (Tuple 1 "One")) None)))
  )

(define-test ordmap-set-test ()
  (let a = (the (ordmap:OrdMap Integer String)
                (list->ordmap
                 (make-list (Tuple 1 "Ichi")
                            (Tuple 4 "Yon")
                            (Tuple 5 "Go")
                            (Tuple 9 "Kyuu")
                            (Tuple 10 "Juu")
                            (Tuple 14 "Juuyon")))))
  (let b = (the (ordmap:OrdMap Integer String)
                (list->ordmap
                 (make-list (Tuple 2 "Dul")
                            (Tuple 3 "Set")
                            (Tuple 5 "Daseot")
                            (Tuple 7 "Ilgop")
                            (Tuple 9 "Ahop")
                            (Tuple 13 "Yeolset")))))

  (is (== (ordmap->list (ordmap:union a b))
          (make-list (Tuple 1 "Ichi")
                     (Tuple 2 "Dul")
                     (Tuple 3 "Set")
                     (Tuple 4 "Yon")
                     (Tuple 5 "Go")
                     (Tuple 7 "Ilgop")
                     (Tuple 9 "Kyuu")
                     (Tuple 10 "Juu")
                     (Tuple 13 "Yeolset")
                     (Tuple 14 "Juuyon"))))
  (is (== (ordmap->list (ordmap:union b a))
          (make-list (Tuple 1 "Ichi")
                     (Tuple 2 "Dul")
                     (Tuple 3 "Set")
                     (Tuple 4 "Yon")
                     (Tuple 5 "Daseot")
                     (Tuple 7 "Ilgop")
                     (Tuple 9 "Ahop")
                     (Tuple 10 "Juu")
                     (Tuple 13 "Yeolset")
                     (Tuple 14 "Juuyon"))))

  (is (== (ordmap->list (ordmap:intersection a b))
          (make-list (Tuple 5 "Go") (Tuple 9 "Kyuu"))))
  (is (== (ordmap->list (ordmap:intersection b a))
          (make-list (Tuple 5 "Daseot") (Tuple 9 "Ahop"))))

  (is (== (ordmap->list (ordmap:difference a b))
          (make-list (Tuple 1 "Ichi")
                     (Tuple 4 "Yon")
                     (Tuple 10 "Juu")
                     (Tuple 14 "Juuyon"))))

  (is (== (ordmap->list (ordmap:xor b a))
          (make-list (Tuple 1 "Ichi")
                     (Tuple 2 "Dul")
                     (Tuple 3 "Set")
                     (Tuple 4 "Yon")
                     (Tuple 7 "Ilgop")
                     (Tuple 10 "Juu")
                     (Tuple 13 "Yeolset")
                     (Tuple 14 "Juuyon"))))
  )

(define-test ordmap-instance-test ()
  (let a = (the (ordmap:OrdMap Integer String)
                (list->ordmap
                 (make-list (Tuple 0 "Ling")
                            (Tuple 1 "Yi")
                            (Tuple 2 "Er")
                            (Tuple 3 "San")
                            (Tuple 4 "Si")))))

  (is (== (map string:reverse a)
          (list->ordmap
           (make-list (Tuple 0 "gniL")
                      (Tuple 1 "iY")
                      (Tuple 2 "rE")
                      (Tuple 3 "naS")
                      (Tuple 4 "iS")))))
  )
